/*
** Copyright (c) K-Marrade
** All rights reserved.
**
** gdt.c : gdt stuff.
**
** Use with caution.
**
*/
#include "kstd.h"
#include "gdt.h"
#include "tss.h"

static t_segment_descriptor gdt[6];
static t_tss tss;

#define STACK_SIZE 0x4000

t_segment_descriptor* gdt_get_user_data(void)
{
  return &(gdt[4]);
}

void gdt_add_entry(int index,
		   t_uint32 base_addr,
		   t_uint32 seg_limit,
		   char access_byte,
		   char flags)
{
  gdt[index].seg_limit = seg_limit;
  gdt[index].base_addr = base_addr;
  gdt[index].base = base_addr >> 16;
  gdt[index].type = access_byte;
  gdt[index].descriptor = access_byte >> 4;
  gdt[index].dpl = access_byte >> 5;
  gdt[index].present = access_byte >> 7;
  gdt[index].limit = seg_limit >> 16;
  gdt[index].avl = flags;
  gdt[index].x64 = flags >> 1;
  gdt[index].default_op_size = flags >> 2;
  gdt[index].granularity = flags >> 3;
  gdt[index].base2 = base_addr >> 24;
}

void gdt_init_userland(t_uint32 code_base_addr,
		       t_uint32 code_limit,
		       t_uint32 data_base_addr,
		       t_uint32 data_limit)
{
  tss.iomap = (unsigned short) sizeof (t_tss);
  tss.ss0 = 0x10; // offset of kernel data segment in gdt

  asm volatile ("movl %%esp, %%eax\n\t"
		: "=a" (tss.esp0)
		:
		:);

  // user code segment
  gdt_add_entry(3, code_base_addr, code_limit, 0xFE, 0xD);

  // user data segment
  gdt_add_entry(4, data_base_addr, data_limit + STACK_SIZE, 0xF2, 0xD);

  // TSS
  gdt_add_entry(5, &tss, sizeof (t_tss), 0x89, 0xD);

  asm volatile ("movw $0x28, %ax\n\t" // offset of tss segment in gdt
  		"ltr %ax\n\t"); // load tss

}

void gdt_init (void)
{
  t_gdtr gdtr;

  *((t_uint64*) &(gdt[0])) = 0x0;

  // kernel code segment
  gdt_add_entry(1, 0, 0xFFFFFFFF, 0x9E, 0xD);

  // kernel data segment
  gdt_add_entry(2, 0, 0xFFFFFFFF, 0x92, 0xD);

  gdtr.limit = sizeof (gdt) - 1;
  gdtr.gdt = (t_uint32) &gdt;

  /* Sets the gdtr register to the gdt adress. */
  __asm__ ("lgdt %0"
	   : /* Outputs */
	   : "m" (gdtr) /* Inputs */
	   : /* Clobber registers */);

  /* Switch to protected mode. */
  __asm__ ("movl %cr0, %eax\n\t"
	   "or   $0x1, %eax\n\t"
	   "movl %eax, %cr0\n\t"
	   "movw $0x10, %ax\n\t"
	   "movw %ax, %ds\n\t"
	   "movw %ax, %ss\n\t"
	   "push $0x08\n\t"
	   "push $1f\n\t"
	   "lret\n\t"
	   "1:\n\t");
}
